﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using HtmlAgilityPack;

namespace Toolbox.SimiliBrowser
{
    public partial class Browser : Component
    {
        private CookieCollection cookies = new CookieCollection();
        private HtmlDocument htmld = new HtmlDocument();

        [Browsable(false)]
        public HtmlDocument CurrentDocument
        {
            get { return this.htmld; }
        }

        private string data;

        [Browsable(false)]
        public string DownloadedData
        {
            get { return this.data; }
        }

        private Uri page;

        [Browsable(false)]
        public string CurrentPage
        {
            get { return this.page is Uri ? this.page.AbsoluteUri : null; }
        }

        private string accept;

        [DefaultValue("*/*;q=0.8")]
        public string HttpAccept
        {
            get { return this.accept is string && this.accept.Length > 0 ? this.accept : "*/*;q=0.8"; }
            set { this.accept = value; }
        }

        public Browser()
        {
            InitializeComponent();
        }

        public void GetIfNot(string u)
        {
            if (this.CurrentPage != u)
            {
                this.Get(new Uri(u));
            }
        }

        public void Get(string u)
        {
            this.Get(new Uri(u));
        }

        public void Get(string u, params string[] formdata)
        {
            this.Get(new Uri(this.page, u), formdata);
        }

        public void Get(Uri u, params string[] formdata)
        {
            this.data = "";
            string query = "";

            foreach (string fd in formdata)
            {
                string[] d = fd.Split(new char[] { '=' }, 2);
                query += (query.Length > 0 ? "&" : "") + UpperCaseUrlEncode(d[0]) + "=" + (d.Length > 1 ? UpperCaseUrlEncode(d[1]) : "");
            }

            this.page = new Uri(u, (query.Length > 0 ? "?" + query : ""));

            HttpWebRequest wr = CreateRequest(this.page, null);
            wr.AllowAutoRedirect = true;

            using (HttpWebResponse response = wr.GetResponse() as HttpWebResponse)
            {
                if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Found)
                {
                    throw new OperationCanceledException("http error: <" + response.StatusCode + ">" + response.StatusDescription);
                }

                this.LoadHttpCookies(response);
                this.LoadHttpData(response);
            }
        }

        public bool SetValueById(string element, string value)
        {
            return this.SetValue(this.htmld.GetElementbyId(element), value);
        }

        public bool SetValueByName(string element, string value)
        {
            return this.SetValue(this.htmld.DocumentNode.SelectSingleNode("//input[@name='" + element + "']|//select[@name='" + element + "']"), value);
        }

        public bool SetValue(HtmlNode n, string value)
        {
            if (n is HtmlNode && n.Name == "select")
            {
                foreach (HtmlNode o in n.SelectNodes("option"))
                {
                    o.SetAttributeValue("selected", o.GetAttributeValue("value", "").Equals(value) ? "selected" : "");
                }
                return true;
            }

            if (n is HtmlNode && n.Name == "input")
            {
                switch (n.GetAttributeValue("type", ""))
                {
                    case "radio":
                        n.SetAttributeValue("checked", n.GetAttributeValue("value", "").Equals(value) ? "checked" : "");
                        break;
                    default:
                        n.SetAttributeValue("value", value);
                        break;
                }
                n.SetAttributeValue("value", value);
                return true;
            }

            return false;
        }

        public void SetRadioByName(string element, string value)
        {
            foreach (HtmlNode o in this.htmld.DocumentNode.SelectNodes(string.Concat("//input[@name='", element, "']")))
            {
                this.SetValue(o, value);
            }
        }

        public void Submit(string buttonName, params string[] additionnalData)
        {
            Dictionary<string, string> formdata = new Dictionary<string, string>();

            foreach (string ad in additionnalData)
            {
                string[] a = ad.Split(new char[] { '=' }, 2);

                if (!formdata.ContainsKey(a[0]))
                {
                    formdata.Add(a[0], a[0] + "=" + (a.Length > 1 ? a[1] : ""));
                }
            }

            foreach (HtmlNode f in this.htmld.DocumentNode.SelectNodes("//form"))
            {
                if (buttonName == null || f.SelectSingleNode("//*[@name='" + buttonName + "']") is HtmlNode)
                {
                    HtmlNodeCollection selects = f.SelectNodes("//select[string-length(@name)>0]");

                    if (selects is HtmlNodeCollection)
                    {
                        foreach (HtmlNode s in selects)
                        {
                            string n = s.GetAttributeValue("name", "");

                            foreach (HtmlNode o in s.SelectNodes("option"))
                            {
                                if (n.Length > 0 && o.GetAttributeValue("selected", "").Length > 0 && !formdata.ContainsKey(n))
                                {
                                    formdata.Add(n, n + "=" + o.GetAttributeValue("value", ""));
                                }
                            }
                        }
                    }

                    HtmlNodeCollection inputs = f.SelectNodes("//input[string-length(@name)>0]");

                    if (inputs is HtmlNodeCollection)
                    {
                        foreach (HtmlNode i in inputs)
                        {
                            string n = i.GetAttributeValue("name", "");
                            string v = i.GetAttributeValue("value", "");
                            string t = i.GetAttributeValue("type", "");

                            switch (t)
                            {
                                case "radio":
                                    if (n.Length > 0 && i.GetAttributeValue("checked", "").Length > 0 && !formdata.ContainsKey(n))
                                    {
                                        formdata.Add(n, n + "=" + v);
                                    }
                                    break;

                                case "submit":
                                    if (n.Length > 0 && n.Equals(buttonName) && !formdata.ContainsKey(n))
                                    {
                                        formdata.Add(n, n + "=" + v);
                                    }
                                    break;

                                case "text":
                                case "password":
                                    if (n.Length > 0 && !formdata.ContainsKey(n))
                                    {
                                        formdata.Add(n, n + "=" + v);
                                    }
                                    break;

                                default:
                                    if (n.Length > 0 && v.Length > 0 && !formdata.ContainsKey(n))
                                    {
                                        formdata.Add(n, n + "=" + v);
                                    }
                                    break;
                            }
                        }
                    }

                    if (f.GetAttributeValue("method", "post").ToLower().Equals("post"))
                    {
                        Uri p = new Uri(this.page, f.GetAttributeValue("action", "."));
                        this.Post(p, formdata.Values.ToArray());
                    }

                    if (f.GetAttributeValue("method", "get").ToLower().Equals("get"))
                    {
                        Uri g = new Uri(this.page, f.GetAttributeValue("action", "."));
                        this.Get(g, formdata.Values.ToArray());
                    }

                    break;
                }
            }

        }

        public void PostBack(string target, string argument = "")
        {
            this.SetValueByName("__EVENTTARGET", target);
            this.SetValueByName("__EVENTARGUMENT", argument);
            this.Submit(null);
        }

        public void Post(string u)
        {
            this.Post(new Uri(u));
        }

        public void Post(Uri u, params string[] formdata)
        {
            this.data = "";
            string query = "";

            foreach (string fd in formdata)
            {
                string[] d = fd.Split(new char[] { '=' }, 2);
                query += (query.Length > 0 ? "&" : "") + UpperCaseUrlEncode(d[0]) + "=" + (d.Length > 1 ? UpperCaseUrlEncode(d[1]) : "");
            }

            HttpWebRequest wr = CreateRequest(u, query);

            using (HttpWebResponse response = wr.GetResponse() as HttpWebResponse)
            {
                switch (response.StatusCode)
                {
                    case HttpStatusCode.OK:

                        this.page = response.ResponseUri;
                        this.LoadHttpCookies(response);
                        this.LoadHttpData(response);
                        break;

                    case HttpStatusCode.Found:

                        this.page = response.ResponseUri;
                        this.LoadHttpCookies(response);
                        this.Get(new Uri(this.page, response.Headers.Get("Location")));
                        break;

                    default:
                        throw new HttpUnhandledException();
                }
            }
        }

      

        private HttpWebRequest CreateRequest(Uri u, string data)
        {
            HttpWebRequest wr = HttpWebRequest.Create(u) as HttpWebRequest;

            wr.Accept = this.accept is string && this.accept.Length > 0 ? this.accept + "," : "" + "*/*;q=0.8";
            wr.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)";
            //wr.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            wr.KeepAlive = true;
            wr.Referer = this.page is Uri ? this.page.AbsoluteUri : "";
            wr.AllowAutoRedirect = false;
            wr.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);

            if (this.cookies.Count > 0)
            {
                wr.CookieContainer = new CookieContainer();
                wr.CookieContainer.Add(this.cookies);
            }

            if (data is string && data.Length > 0)
            {
                wr.Method = "POST";
                wr.ContentType = "application/x-www-form-urlencoded";
                wr.ContentLength = data.Length;

                using (Stream ws = wr.GetRequestStream())
                {
                    UTF8Encoding encoding = new UTF8Encoding();
                    byte[] bytes = encoding.GetBytes(data);
                    ws.Write(bytes, 0, bytes.Length);
                }
            }

            return wr;
        }

        private void LoadHttpData(HttpWebResponse response)
        {
            if (response.ResponseUri.Scheme == "http" && response.ContentType.StartsWith("text"))
            {
                using (Stream s = response.GetResponseStream())
                {
                    Encoding encode = FindEncoding(response);

                    using (StreamReader sr = new StreamReader(s, encode))
                    {
                        this.data = sr.ReadToEnd();
                    }
                }
            }

            if (this.data.Length > 0 && response.ContentType.Contains("html"))
            {
                this.htmld.LoadHtml(this.data);
            }
        }

        private void LoadHttpCookies(HttpWebResponse wr)
        {
            string cookiestring = wr.Headers.Get("Set-Cookie");

            if (cookiestring is string && cookiestring.Length > 0)
            {
                string[] parts = cookiestring.Split(';');
                string[] data = parts[0].Split('=');
                string[] host = this.page.Host.Split('.');

                Cookie c = new Cookie(data[0], data[1], "/", "." + string.Join(".", host, host.Length - 2, 2));

                for (int i = 1; i < parts.Length; i++)
                {
                    data = parts[i].Split('=');

                    switch (data[0].Trim().ToLower())
                    {
                        case "path":
                            c.Path = data[1];
                            break;
                        case "domain":
                            c.Domain = data[1];
                            break;
                        default:
                            break;
                    }
                }



                this.cookies.Add(c);
            }
        }

        public static string UpperCaseUrlEncode(string s)
        {
            char[] temp = HttpUtility.UrlEncode(s).ToCharArray();
            for (int i = 0; i < temp.Length - 2; i++)
            {
                if (temp[i] == '%')
                {
                    temp[i + 1] = char.ToUpper(temp[i + 1]);
                    temp[i + 2] = char.ToUpper(temp[i + 2]);
                }
            }
            return new string(temp);
        }



        private static Encoding FindEncoding(HttpWebResponse wr)
        {
            Encoding encode = System.Text.Encoding.UTF8;

            string[] ct = wr.ContentType.Split(';');

            foreach (string cts in ct)
            {
                if (cts.Trim().StartsWith("charset="))
                {
                    encode = System.Text.Encoding.GetEncoding(cts.Trim().Substring(8).Trim());
                    break;
                }
            }

            return encode;
        }
    }
}
